package org.sampleSpringFramework.mvc.processor.impl;

import lombok.extern.slf4j.Slf4j;
import org.sampleSpringFramework.core.BeanContainer;
import org.sampleSpringFramework.mvc.annotation.RequestMapping;
import org.sampleSpringFramework.mvc.annotation.RequestParam;
import org.sampleSpringFramework.mvc.annotation.ResponseBody;
import org.sampleSpringFramework.mvc.common.ControllerMethod;
import org.sampleSpringFramework.mvc.common.ModelAndView;
import org.sampleSpringFramework.mvc.common.RequestPathInfo;
import org.sampleSpringFramework.mvc.processor.RequestProcessor;
import org.sampleSpringFramework.mvc.processor.RequestProcessorChain;
import org.sampleSpringFramework.mvc.render.ResultRender;
import org.sampleSpringFramework.mvc.render.impl.JsonResultRender;
import org.sampleSpringFramework.mvc.render.impl.ResourceNotFoundResultRender;
import org.sampleSpringFramework.mvc.render.impl.ViewResultRender;
import org.sampleSpringFramework.util.ConvertUtil;
import org.sampleSpringFramework.util.ValidationUtil;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static jdk.nashorn.api.scripting.ScriptUtils.convert;

/**
 * Controller请求处理器
 */
@Slf4j
public class ControllerRequestProcessor implements RequestProcessor {

	/**
	 * 容器实例，需要从容器中获取处理请求的Controller类
	 */
	private BeanContainer beanContainer;

	/**
	 * 维护映射管理：key(请求) value(处理请求的controller的方法)
	 */
	private Map<RequestPathInfo, ControllerMethod> requestControllerMethodMap = new ConcurrentHashMap<>();

	/**
	 * 构造函数：利用容器的能力，在初始化的时候，完成对request-controller关系的映射
	 */
	public ControllerRequestProcessor(){
		// 注意：dispatcherServlet在第一次访问的时候已经初始化过1次容器了，这里会直接使用第一次初始化的容器
		// 同时，ControllerRequestProcessor的初始化在DI和AOP之后，所以此时容器中的bean是完备的bean实例
		this.beanContainer = BeanContainer.getInstance();
		Set<Class<?>> requestMappingClassesSet = beanContainer.getClassesByAnnotation(RequestMapping.class);
		initMapForRequestAndControllerMethod(requestMappingClassesSet);
	}

	/**
	 * 在ControllerRequestProcessor构造函数被调用的时候执行，完成request-controller方法的映射
	 * @param requestMappingClassesSet 标记了指定注解的集合
	 */
	private void initMapForRequestAndControllerMethod(Set<Class<?>> requestMappingClassesSet) {
		// 1、遍历@RequestMapping标记的所有类Class，将其value属性值作为一级路径
		if(ValidationUtil.isEmpty(requestMappingClassesSet)) return;
		for(Class<?> requestMappingClass: requestMappingClassesSet){
			RequestMapping requestMapping = requestMappingClass.getAnnotation(RequestMapping.class);
			// 以下两行代码测试使用：结论就是getAnnotation创建的注解实例，是jdk动态代理生成的，
			// 所以，requestMapping.getClass() 和 RequestMapping.class 结果不同
//			System.out.println(requestMapping.getClass());//class com.sun.proxy.$Proxy10
//			System.out.println(RequestMapping.class);//interface org.sampleSpringFramework.mvc.annotation.RequestMapping
			// 让@RequestMapping("abc")转换成@RequestMapping("/abc")
			String basePath = requestMapping.value();
			if(!(basePath.startsWith("/"))){
				basePath = "/" + basePath;
			}
			// 2、遍历该类中@RequestMapping标记的所有方法Method，将其value属性值作为二级路径
			Method[] requestMappingMethods = requestMappingClass.getDeclaredMethods();
			if(ValidationUtil.isEmpty(requestMappingMethods)) return;//没有一个method标记了@RequestMapping
			for(Method method: requestMappingMethods){
				if(method.isAnnotationPresent(RequestMapping.class)){//先判断该方法有无标记@RequestMapping注解
					RequestMapping annotation = method.getAnnotation(RequestMapping.class);
					String methodPath = annotation.value();
					if(!(methodPath.startsWith("/"))){
						methodPath = "/" + methodPath;
					}
					String url = basePath + methodPath;
					// 3、解析方法中标记了@RequestParam的参数，获取注解的value属性值，作为客户端传递的参数的名字
					// 获取被标记的参数的数据类型，建立参数名和参数类型的映射
					Parameter[] parameters = method.getParameters();
					Map<String,Class<?>> paramNameAndTypeMap = new HashMap<>();
					if(!ValidationUtil.isEmpty(parameters)) {
						for (Parameter parameter : parameters) {
							RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
							if (requestParam == null) {
								throw new RuntimeException("自研框架目前要求：方法的所有参数都必须要有@RequestParam标签");
							}
							String paramName = requestParam.value();
							Class<?> paramType = parameter.getType();
							paramNameAndTypeMap.put(paramName, paramType);
						}
					}
					// 4、将解析到的信息，转换成RequestPathInfo、ControllerMethod实例，维护在requestControllerMethodMap中
					String requestMethod = String.valueOf(annotation.REQUEST_METHOD());
					RequestPathInfo requestPathInfo = new RequestPathInfo(url, requestMethod);
					if(requestControllerMethodMap.containsKey(requestPathInfo)){
						log.warn("已经存在处理该请求的controller方法，但是之前的方法会被覆盖，由最新的controller方法处理请求：" +
								""+requestPathInfo.getPath());
					}
					// 构造ControllerMethod实例，那个controller类、哪个方法、方法的参数
					ControllerMethod controllerMethod = new ControllerMethod(requestMappingClass, method, paramNameAndTypeMap);
					// 维护request、controller到map中
					requestControllerMethodMap.put(requestPathInfo,controllerMethod);
					}
				}
			}

	}

	@Override
	public boolean process(RequestProcessorChain requestProcessorChain) throws Exception {
		// 1、根据HttpRequestServlet获取path和method，构造RequestPathInfo实例
		// 从Map<RequestPathInfo，ControllerMethod>获取ControllerMethod实例
		String requestPath = requestProcessorChain.getRequestPath();
		String requestMethod = requestProcessorChain.getRequestMethod();
		ControllerMethod controllerMethod = requestControllerMethodMap.get(new RequestPathInfo(requestPath, requestMethod));
		if(controllerMethod == null){
			// 没有找到处理该请求的controller方法，直接渲染为ResourceNotFoundResultRender
			requestProcessorChain.setResultRender(new ResourceNotFoundResultRender(requestPath,requestMethod));
			return false;
		}
		// 2、解析请求中的参数，交由ControllerMethod的method执行
		Object result = invokeControllerMethod(controllerMethod,requestProcessorChain.getRequest());
		// 3、根据返回结果采用不同的Render进行渲染
		setResultRender(result,controllerMethod,requestProcessorChain);
		return true;
	}

	/**
	 * 解析请求中的参数，交由ControllerMethod的method执行
	 * @param controllerMethod 处理请求的controller方法
	 * @param servletRequest 待处理的请求
	 * @return
	 */
	private Object invokeControllerMethod(ControllerMethod controllerMethod, HttpServletRequest servletRequest) throws InstantiationException {
		// 1、获取到参数名：参数值（一个参数名，可能有多个参数值）
		Map<String, String[]> parameterMap = servletRequest.getParameterMap();
		Map<String,String> paramNameAndValueMap = new HashMap<>();//维护1个参数名，1个参数值的map
		if(!ValidationUtil.isEmpty(parameterMap)){
			for(Map.Entry<String,String[]> paramEntry: parameterMap.entrySet()){
				// 注意：自研MVC框架暂时只支持访问的url中，1个参数名=1个参数值，所以只取出[0]
				paramNameAndValueMap.put(paramEntry.getKey(),paramEntry.getValue()[0]);
			}
		}
		// 2、根据paramNameAndValueMap中的<string参数名，string参数值>，和controllerMethod中维护的<string参数名，class参数类型>
		// 构造该方法的参数列表
		List<Object> paramList = new ArrayList<>();
		Map<String, Class<?>> ParamNameAndTypeMap = controllerMethod.getMethodParam();
		Object paramValue;
		if(!ValidationUtil.isEmpty(ParamNameAndTypeMap)) {
			for (String paramName : ParamNameAndTypeMap.keySet()) {
				Class<?> type = ParamNameAndTypeMap.get(paramName);//参数类型
				String value = paramNameAndValueMap.get(paramName);//参数值
				if (value == null) {//请求的url只有参数名，没有写参数值，必须设置默认值，目前自研框架只支持string、和8种基本数据类型及其包装类
					paramValue = ConvertUtil.defaultConvert(type);//赋默认值
				} else {
					paramValue = ConvertUtil.paramConvert(type, value);//赋值
				}
				paramList.add(paramValue);//参数值添加到列表
			}
		}
		// 3、执行Controller中的方法并返回结果
		Object controller = beanContainer.getBean(controllerMethod.getControllerClass());
		Method method = controllerMethod.getInvokedMethod();
		method.setAccessible(true);
		Object result = null;
		try {
			if(paramList.size() == 0){
				result = method.invoke(controller);//执行的方法没有参数
			}
			else{
				result = method.invoke(controller,paramList.toArray());//执行的方法有多个参数
			}
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		} catch (InvocationTargetException e) {
			e.printStackTrace();
			throw new RuntimeException(e.getTargetException());//抛出异常是为了让doRequestProcessorChain捕获，设置Render
		}
		return result;
	}

	/**
	 * 根据返回结果采用不同的Render进行渲染
	 * @param result 返回结果
	 * @param controllerMethod 处理请求的controller方法
	 * @param requestProcessorChain 请求处理链
	 */
	private void setResultRender(Object result, ControllerMethod controllerMethod, RequestProcessorChain requestProcessorChain) {
		if(result == null) {
			return;
		}
		ResultRender resultRender;
		// 查看方法上是否标记了@Responsebody注解，有的话使用JsonRender
		Method method = controllerMethod.getInvokedMethod();
		boolean isJson = method.isAnnotationPresent(ResponseBody.class);
		if(isJson){
			resultRender = new JsonResultRender(result);//json渲染
		}else{
			resultRender = new ViewResultRender(result);//view渲染
		}
		requestProcessorChain.setResultRender(resultRender);
	}
}
